Multidimensional Array

by Larry Rix (modified: 2014 Jan 15)

Recently, I came upon a need for a multidimensional array. I immediately turned to Google to see if I could locate something already designed, coded, and tested. To my dismay, I found only a smattering of hints, discussions, and some skeletal code (e.g. www.computer-programming-forum.com/21-eiffel and then look for multidimensional array and a submitter named "ralmi").

The need in the use case before me did not magically "go away", so I needed to code something. Using an "array-of-array's" was not kosher because the ARRAY and ARRAYED_LIST classes et al did not provide me with a clean and simple API. Using the skeletal code found on the computer programming forum, I decided to see if I could make a well crafted class with a simple API. Below is what I came up with.

On quick self-review, I can see that there is a potential bug if the boundaries of any dimension of the array are <= 0. I am not managing these ranges properly. The assumption is that all array boundaries are > 1.

Calling the API is pretty simple:

test_arrayn -- New test routine local l_array_n: ARRAYN [INTEGER] do create l_array_n.make_n_based (<<[1,2], [1,2], [1,2]>>) create l_array_n.make_one_based (<<2, 2, 2>>) end

Once the ARRAYN is created (e.g. l_array_n), then it is open for access:

local l_item: MY_ITEM l_array_n: ARRAYN [MY_ITEM] do create l_array_n.make_one_based (<<3,3,3>>) l_array_n.put (create {MY_ITEM}, <<1,2,3>>) l_item := l_array_n.item (<<1,2,3>>) end

note description: "[ Representation of a Multi-dimensional Array. ]" date: "$Date: $" revision: "$Revision: $" class ARRAYN [G -> ANY] create make_n_based, make_n_based_filled, make_one_based, make_one_based_filled feature {NONE} -- Initialization make_one_based_filled (a_nb: ARRAY [INTEGER]; v: G) -- Initialize Current as a one-based lower bound on all dimensional vectors -- and then filled with `v'. do make_one_based (a_nb) across 1 |..| internal_items.count as ic_index loop internal_items.put (v, ic_index.item) end end make_one_based (a_nb: ARRAY [INTEGER]) -- Initialize Current as a one-based lower bound on all dimensional vectors. local l_bounds: like bounds i: INTEGER do create l_bounds.make_filled ([0, 0], 1, a_nb.count) from i := 1 until i > a_nb.count loop l_bounds.put ([1, a_nb [i]], i) i := i + 1 end make_n_based (l_bounds) end make_n_based_filled (a_nb: like bounds; v: G) -- Initialize Current filled with `v' items and with `a_nb' like `bounds' do make_n_based (a_nb) across 1 |..| internal_items.count as ic_index loop internal_items.put (v, ic_index.item) end end make_n_based (a_nb: like bounds) -- Initialize Current with `a_nb' like `bounds'. local l_element_count, l_plane_count, i: INTEGER do from bounds := a_nb i := 1 l_element_count := 1 until i > a_nb.count loop l_plane_count := ((a_nb [i].upper_nb - a_nb [i].lower_nb) + 1) l_element_count := l_element_count * l_plane_count i := i + 1 end create internal_items.make_filled (create {ANY}, 1, l_element_count) end feature -- Access bounds: ARRAY [TUPLE [lower_nb, upper_nb: INTEGER]] -- Lower and Upper bounds of Current. dimensions: INTEGER -- Number of dimensions in Current `bounds' do Result := bounds.count end max_size: INTEGER -- Maximum linear size of `internal_items'. --| Contract support for revealing maximum linear size for Clients of Current. local i: INTEGER once from i := 1 Result := 1 until i > dimensions loop Result := Result * bounds [i].upper_nb i := i + 1 end end item (a_vector: ARRAY [INTEGER]): G -- Item @ `a_vector'. require is_valid_vector: is_valid_vector (a_vector) do check attached_as_g: attached {G} internal_items [location (a_vector)] as al_item then Result := al_item end end is_valid_vector (a_vector: ARRAY [INTEGER]): BOOLEAN -- Is `a_vector' valid, based on `bounds'? do Result := a_vector.count <= dimensions Result := Result and across a_vector as ic_vector all ic_vector.item >= bounds [ic_vector.cursor_index].lower_nb and ic_vector.item <= bounds [ic_vector.cursor_index].upper_nb end Result := Result and location (a_vector) <= max_size end put (a_object: G; a_vector: ARRAY [INTEGER]) -- Put `object' at `index' require is_valid_vector: is_valid_vector (a_vector) do internal_items.put (a_object, location (a_vector)) end location (a_vector: ARRAY [INTEGER]): INTEGER -- The linear location of element in `internal_items' at `a_vector'. require is_valid_vector: is_valid_vector (a_vector) local i: INTEGER do from i := 1 Result := 1 until i > dimensions loop Result := Result * a_vector [i] i := i + 1 end end feature {NONE} -- Implementation internal_items: ARRAY [ANY] -- Internal storage of items for Current. ;end

Comments
  • Larry Rix (11 years ago 17/1/2014)

    A Deeper Flaw than I Thought

    Turns out that there is a deeper flaw than I originally thought with the code above. I am working on a correction now and will hopefully have it coded up soon.

    The problem is with the calculation of `location': Vector <<1, 2, 1>> is the same location Result as <<2, 1, 1>> -- that is -- 1 x 2 x 1 = 2 and 2 x 1 x 1 = 2. In a 3-dimensional array, this will cause and error, which is precisely what happened when I finally added a precondition to inspect the target location element in the internal_items array to ensure it was empty (e.g. not attached {G} item (a_vector)).

    So, what I have to do now is develop and internal structure that is more careful about where it places the items in the internal_items array. I am told that the C compiler does this with the code it generates. I will need to explore and perhaps exploit this algorithm.

    • Larry Rix (11 years ago 17/1/2014)

      The actual solution ...

      place (a_object: G; a_vector: ARRAY [INTEGER]) -- Place `a_object' at an empty `a_vector' location. require empty_location: not attached {G} item (a_vector) do put (a_object, a_vector) ensure placed: attached {G} item (a_vector) as al_item and then al_item ~ a_object end replace (a_object: G; a_vector: ARRAY [INTEGER]) -- Place or replace `a_object' at `a_vector' location. do put (a_object, a_vector) end put (a_object: G; a_vector: ARRAY [INTEGER]) -- Put `a_object' at `a_vector' require is_valid_vector: is_valid_vector (a_vector) do internal_items.put (a_object, location (a_vector)) ensure item_at_location: internal_items [location (a_vector)] ~ a_object end location (a_vector: ARRAY [INTEGER]): INTEGER -- The linear location of element in `internal_items' at `a_vector'. require two_or_more: dimensions > 1 is_valid_vector: is_valid_vector (a_vector) local i, l_segment_size: INTEGER do from i := 1 l_segment_size := max_size until i = dimensions loop l_segment_size := (l_segment_size / (bounds [i].upper_nb - bounds [i].lower_nb + 1)).truncated_to_integer Result := Result + l_segment_size * (a_vector [i] - bounds [i].lower_nb) i := i + 1 end Result := Result + a_vector [i] end